package com.otom.bcel;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;

import com.otom.bcel.annotations.CollectionHint;
import com.otom.bcel.annotations.CollectionIndexHint;
import com.otom.bcel.annotations.CollectionInfo;
import com.otom.bcel.annotations.MapCollectionList;
import com.otom.bcel.annotations.MapFieldList;
import com.otom.bcel.annotations.MapTo;

public class FieldMapping extends BaseObject {
	private FieldInfo source;
	private FieldInfo destination;
	private boolean mappedClass;
	private String postCallBack;

	public String getPostCallBack() {
		return postCallBack;
	}

	public void setPostCallBack(String callBackMethod) {
		this.postCallBack = callBackMethod;
	}

	public boolean isMappedClass() {
		return mappedClass;
	}

	public void setMappedClass(boolean mappedClass) {
		this.mappedClass = mappedClass;
	}

	public FieldInfo getSource() {
		return source;
	}

	public void setSource(FieldInfo source) {
		this.source = source;
	}

	public FieldInfo getDestination() {
		return destination;
	}

	public void setDestination(FieldInfo destination) {
		this.destination = destination;
	}

	public void initFromAnnotations(ClassInfo classInfo, Field field) {
		initSource(classInfo, field);
		initDestination(classInfo, field);
	}

	private void initDestination(ClassInfo classInfo, Field field) {
		MapTo fieldMapper = getFieldMapper(classInfo, field);
		CollectionInfo collectionInfo = getCollectionType(classInfo, field);
		
		initDestination(classInfo, fieldMapper, collectionInfo);
	}

	private CollectionInfo getCollectionType(ClassInfo classInfo, Field field) {
		CollectionInfo collectionInfoMapper = null;
		CollectionHint collectionHint = field.getAnnotation(CollectionHint.class);
		
		if (collectionHint != null) {
			for (CollectionInfo collectionInfo : collectionHint.target()) {
				if (collectionInfo != null
						&& (collectionInfo.forClass() == void.class || collectionInfo
								.forClass() == classInfo.getDestination())) {					
					collectionInfoMapper = collectionInfo;
					break;
				}
			}
		}
		
		
		if (collectionInfoMapper == null) {
			MapCollectionList mapCollectionList = field.getAnnotation(MapCollectionList.class);
		
			if (mapCollectionList != null) {
				outer:
				for (CollectionHint collectionHint1 : mapCollectionList.value()) {
					for (CollectionInfo collectionInfo : collectionHint1.target()) {
						if (collectionInfo != null
								&& (collectionInfo.forClass() == void.class || collectionInfo
										.forClass() == classInfo.getDestination())) {
							collectionInfoMapper = collectionInfo;							
							break outer;
						}
					}
				}
			}			
		}
		
		return collectionInfoMapper;
	}

	private MapTo getFieldMapper(ClassInfo classInfo, Field field) {
		MapTo fieldMapper = field.getAnnotation(MapTo.class);
		
		if (fieldMapper != null
				&& !(fieldMapper.forClass() == void.class || fieldMapper
						.forClass() == classInfo.getDestination())) {
			fieldMapper = null;
		}
		
		if (fieldMapper == null) {
			MapFieldList fieldMapperList = field.getAnnotation(MapFieldList.class);
			
			if (fieldMapperList != null) {
				for (MapTo mapTo : fieldMapperList.value()) {
					if (mapTo != null
							&& (mapTo.forClass() == void.class || mapTo
									.forClass() == classInfo.getDestination())) {
						fieldMapper = mapTo;
						break;
					}				
				}
			}
		}
		return fieldMapper;
	}

	private void initDestination(ClassInfo classInfo, MapTo fieldMapper,
			CollectionInfo collectionType) {
		FieldInfo destination = new FieldInfo();
		this.destination = destination;
		destination.setClazz(classInfo.getDestination());
		
		if (collectionType != null) {
			destination.setConcreteCollectionType(collectionType.impl());
			destination.setElementType(collectionType.type());
			
			if (!collectionType.convert().equals("")) {
				destination.setCollectionObjectConverter(collectionType.convert());
			}
		}
		
		if (fieldMapper != null) {
			CollectionIndexHint indexHints = fieldMapper.hints();
			if (indexHints != null) {
				destination.setCollectionTypeHintList(new ArrayList<CollectionInfo>(
						Arrays.asList(fieldMapper.hints().value())));
			}

			if (fieldMapper.convertTargetToThis() != null && !fieldMapper.convertTargetToThis().equals("")) {
				destination.setConverterMethod(fieldMapper.convertTargetToThis());
			}
			
			mappedClass = fieldMapper.mappedClass();
			destination.setGetMethod(fieldMapper.targetGetMethod());
			destination.setSetMethod(fieldMapper.targetSetMethod());
			destination.initFromAnnotations(fieldMapper.name());

			if (mappedClass) {
				if (!"".equals(fieldMapper.postCallBack())) {
					postCallBack = fieldMapper.postCallBack();
				}
				generateDependentClasses(classInfo, destination);
			}			
		} else {
			if (classInfo.isMapWildCard()) {
				for (Field destinationField : FieldHelper.getFields(classInfo.getDestination())){
					if (destinationField.getName().equals(source.getName())) {
						destination.initFromAnnotations(source.getName());
						break;
					}
				}
			}
		}
	}

	// TODO - need to refactor
	private void generateDependentClasses(ClassInfo classInfo,
			FieldInfo destination) {
		destination = getDestinationField(destination);
		if (source.isListable() && destination.isListable()) {
			Class<?> sourceType;
			
			if (source.isCollection() || source.isMap()) {
				sourceType = source.getElementType();
			} else {
				sourceType = source.getFieldType().getComponentType();
			}
			
			Class<?> destinationType;
			
			if (destination.isCollection() || destination.isMap()) {
				destinationType = destination.getElementType();
			} else {
				destinationType = destination.getFieldType().getComponentType();
			}
			
			if (FieldHelper.isInterfaceOrAbstractClass(sourceType) && FieldHelper.isInterfaceOrAbstractClass(destinationType)
					&& destination.getImplementors() != null && source.getImplementors() != null) {
				 for (Class<?> sourceclass : source.getImplementors()) {
					for (Class<?> destinationClass : destination.getImplementors()) {
						classInfo.getDependentClasses().add(new ClassMapper(sourceclass, destinationClass));
					}
				 }
			} else if (FieldHelper.isInterfaceOrAbstractClass(destinationType) && destination.getImplementors() != null) {
				for (Class<?> clazz : destination.getImplementors()) {
					classInfo.getDependentClasses().add(new ClassMapper(source.getFieldType(), clazz));
				}
			}
			/*else if (FieldHelper.isInterfaceOrAbstractClass(sourceType) && source.getImplementors() != null) {
				for (Class<?> clazz : source.getImplementors()) {
					classInfo.getDependentClasses().add(new ClassMapper(clazz, destination.getFieldType()));
				}
			}*/
			else {
				classInfo.getDependentClasses().add(new ClassMapper(sourceType, destinationType));
			}
			
		} else if (FieldHelper.isInterfaceOrAbstractClass(source.getFieldType()) && 
				FieldHelper.isInterfaceOrAbstractClass(destination.getFieldType())
				&& destination.getImplementors() != null && source.getImplementors() != null) {
			 for (Class<?> sourceclass : source.getImplementors()) {
				for (Class<?> destinationClass : destination.getImplementors()) {
					classInfo.getDependentClasses().add(new ClassMapper(sourceclass, destinationClass));
				}
			 }
		} else if (FieldHelper.isInterfaceOrAbstractClass(destination.getFieldType()) && destination.getImplementors() != null) {
			for (Class<?> clazz : destination.getImplementors()) {
				classInfo.getDependentClasses().add(new ClassMapper(source.getFieldType(), clazz));
			}
		}
		else {
			classInfo.getDependentClasses().add(new ClassMapper(source.getFieldType(), destination.getFieldType()));
		}
	}

	private void initSource(ClassInfo classInfo, Field field) {
		FieldInfo source = new FieldInfo();
		this.source = source;		
		source.setClazz(classInfo.getSource());
		source.setTargetClass(classInfo.getDestination());
		source.initFromAnnotations(field.getName());
	}
	
	private FieldInfo getDestinationField(FieldInfo destination) {
		while (destination.getChildFieldInfo() != null) {
			destination = destination.getChildFieldInfo();
		}

		return destination;
	}
}